#ifndef WAVE_FRAME_H
#define WAVE_FRAME_H

#include "bitmap.h"

const double epsilon = 1e-12;
const int resolution = 256 ;

constexpr double PI    = 3.141592653589793 ;
constexpr double PI1   = PI / 180.0 ;
constexpr double PI360 = 2.0 * PI ;

#define MAX_PARTICLE_NUMBER 1000000
#define TIME_STEP 0.01f
#define PARTICLE_RADIUS 0.05f
#define WAVE_SPEED 0.5f
#define MAX_AMP 0.005f
#define MIN_AMP 5.0e-5 // 2430 particles per wave
#define CAUSTICS 0.1f

/*
#define DATA_INTERNAL GL_RGBA32F
#define DATA_FORMAT GL_RGBA
#define DATA_TYPE GL_FLOAT
#define TYPE_SIZE 4
*/
#include "gpgpu.h"

int windowWidth = 1024, windowHeight = 768;
double cameraNear = 0.1, cameraFar = 1000.0, cameraFovy = 45.0;
double cameraOR = 1.5, cameraOH = 300.0, cameraOV = 30.0;
int mouseOX, mouseOY;
double cameraR, cameraH, cameraV;
double cameraTR, cameraTH, cameraTV;
float genX, genY;
float eyePos[4];


int systemPaused = 0, cameraSetting = 0, generating = 0, renderParticles = 0, displayMode = 0, transparent = 0, testSwitch = 0;
float fading = 0.0f;


GLuint poolBottom;
GLuint fb, fboTex[8], vertex_buffer, normal_buffer, element_buffer, cb;
GLuint colorAttachment[] = {
	GL_COLOR_ATTACHMENT0,
	GL_COLOR_ATTACHMENT1,
	GL_COLOR_ATTACHMENT2,
	GL_COLOR_ATTACHMENT3,
	GL_COLOR_ATTACHMENT4,
	GL_COLOR_ATTACHMENT5,
	GL_COLOR_ATTACHMENT6,
	GL_COLOR_ATTACHMENT7};


const unsigned  maxIndex = (resolution - 1) * (resolution - 1) * 6;
GLuint indices[maxIndex];

GLfloat lightPos[] = {-0.8f, 0.2f, 1.2f, 1.0f};


#include "waveparticle.h"

class WaveFrame : public math::ui::GLFrame
{
public :
	typedef particle<float> particle_type ;

	WaveFrame () : GLFrame () {}
	virtual void sence () { glutSolidSphere(0.5, 40, 40) ;}

	virtual void reshape(int width, int height)
	{
		glViewport(0, 0, width, height);
		windowWidth = width; windowHeight = height;
		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();
		gluPerspective(cameraFovy, (GLdouble)windowWidth / (GLdouble)windowHeight, cameraNear, cameraFar);
		glMatrixMode(GL_MODELVIEW);
	}
	virtual void initGL ()
	{
		test_gpu();
		load_shader();
		init_shader();
		init_framebuffer ();
	}
	virtual void idle ()
	{
		if (!systemPaused) 
		{
			m_particlesystem.update();
		}
		glutPostRedisplay();
	}

	virtual void display ()
	{
		glMatrixMode(GL_MODELVIEW);
		glLoadIdentity();
		glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

		glTranslatef(0.0f, 0.0f, -cameraR);
		glRotatef(cameraV - 90.0, 1.0f, 0.0f, 0.0f);
		glRotatef(270.0 - cameraH, 0.0f, 0.0f, 1.0f);
		glTranslatef(-0.5f, -0.5f, 0.0f);

		render();
		if (renderParticles || fading > 0.0f) renderWaveParticles();

		glEnable(GL_LIGHT0) ;
		glEnable(GL_LIGHTING);

		glBegin(GL_QUADS);

		// four barrier 
		glNormal3f (0.0, 1.0, 0.0f);
		glVertex3f (0.0, 0.0, -0.2f); 
		glVertex3f (0.0, 0.0, 0.1f);
		glVertex3f (1.0, 0.0, 0.1f); 
		glVertex3f (1.0, 0.0, -0.2f);

		glNormal3f (-1.0, 0.0, 0.0f);
		glVertex3f (1.0, 0.0, -0.2f); 
		glVertex3f (1.0, 0.0, 0.1f);
		glVertex3f (1.0, 1.0, 0.1f); 
		glVertex3f (1.0, 1.0, -0.2f);

		glNormal3f (0.0, -1.0, 0.0f);
		glVertex3f (1.0, 1.0, -0.2f); 
		glVertex3f (1.0, 1.0, 0.1f);
		glVertex3f (0.0, 1.0, 0.1f); 
		glVertex3f (0.0, 1.0, -0.2f);

		glNormal3f (1.0, 0.0, 0.0f);
		glVertex3f (0.0, 1.0, -0.2f); 
		glVertex3f (0.0, 1.0, 0.1f);
		glVertex3f (0.0, 0.0, 0.1f); 
		glVertex3f (0.0, 0.0, -0.2f);


		// extend platform 
		glEnd();
		glDisable(GL_LIGHTING);
		glutSwapBuffers();
	}

	void keyboard(unsigned char key, int x, int y)
	{
		switch (key) 
		{
		case ' ':
			systemPaused = !systemPaused; break; 
		case 'r': case 'R':  // rest
			m_particlesystem.reset();
			glutPostRedisplay();
			break;
		case 'w': case 'W':  // show the particles
			renderParticles = !renderParticles;
			glutPostRedisplay();
			break;
		case 't': case 'T':  // water transparent
			transparent = !transparent;
			glutPostRedisplay();
			break;
		case '0': case '1' : case '2' : case '3' : case '4' : case '5' : case '6' : case '7' : 
			displayMode = key - '0';
			glutPostRedisplay();
			break;
		default:
			break;
		}
	}


	void motion(int x, int y)
	{
		if (generating) 
		{
			clickToGenerate(x, y);
		}
		else if (cameraSetting == 1) {
			cameraH = cameraTH - 0.5 * (x - mouseOX);
			if (cameraH < -360.0) cameraH += 360.0;
			else if (cameraH > 360.0) cameraH -= 360.0;
			cameraV = cameraTV + 0.5 * (y - mouseOY);
			if (cameraV < -90.0 + epsilon) cameraV = -90.0 + epsilon;
			else if (cameraV > 90.0 - epsilon) cameraV = 90.0 - epsilon;
			eyePos[0] = cameraR * cos(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
			eyePos[1] = cameraR * sin(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
			eyePos[2] = cameraR * sin(cameraV * PI1);
		}
		else if (cameraSetting == 2) {
			cameraR = cameraTR + 0.01 * (y - mouseOY);
			if (cameraR < cameraNear) cameraR = cameraNear;
			else if (cameraR > cameraFar) cameraR = cameraFar;
			eyePos[0] = cameraR * cos(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
			eyePos[1] = cameraR * sin(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
			eyePos[2] = cameraR * sin(cameraV * PI1);
		}
		glutPostRedisplay();
	}
	void mouse(int button, int state, int x, int y)
	{
		if (button == GLUT_LEFT_BUTTON) {
			if (state == GLUT_DOWN) {
				clickToGenerate(x, y);
				generating = 1;
			}
			else if (state == GLUT_UP) {
				generating = 0;
			}
		}
		else if (button == GLUT_MIDDLE_BUTTON) {
			if (state == GLUT_DOWN) {
				cameraSetting = 1;
				mouseOX = x; mouseOY = y;
				cameraTH = cameraH; cameraTV = cameraV;
			}
			else if (state == GLUT_UP) {
				cameraSetting = 0;
			}
		}
		else if (button == GLUT_RIGHT_BUTTON) {
			if (state == GLUT_DOWN) {
				cameraSetting = 2;
				mouseOX = x; mouseOY = y;
				cameraTR = cameraR;
			}
			else if (state == GLUT_UP) {
				cameraSetting = 0;
			}
		}
	}

protected :
	void draw_unitsquare ()
	{
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f); 		glVertex2f(0.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f);		glVertex2f(1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f);		glVertex2f(1.0f, 1.0f);
		glTexCoord2f(0.0f, 1.0f);		glVertex2f(0.0f, 1.0f);
		glEnd();
	}
	void render_framebuffer (Shader & shader, GLuint color_attachment)
	{
		glDrawBuffer(color_attachment);
		shader.bind();
		draw_unitsquare();
		shader.unbind();
	}
	void render_texture (GLuint color_attachment, GLuint & texture)
	{
		glReadBuffer(color_attachment);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, texture);
		glReadPixels(0, 0, resolution, resolution, DATA_FORMAT, DATA_TYPE, 0);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);
		glReadBuffer(GL_NONE);
	}
	void render_particles ()
	{
		glDrawBuffer(GL_COLOR_ATTACHMENT0);
		glBlendFunc(GL_ONE, GL_ONE);
		glEnable(GL_BLEND);
		glClear(GL_COLOR_BUFFER_BIT);
		glBegin(GL_POINTS);

		for (unsigned i = 0; i < m_particlesystem.get_size(); ++ i)
		{
			const particle_type & p = m_particlesystem[i] ;
			float systime = m_particlesystem.get_system_time();  
			float t =  systime - p.bt;
			switch (displayMode)
			{
			case 1 : glColor3f(1.0f, 0.0f, 1.0f); break; 
			case 2 : glColor3f(0.0f, 0.0f, p.pos.z * 100.0f); break; 
			case 3 : glColor3f(0.0f, 0.0f, p.pos.z * 100.0f); break ;
			default: glColor3f(0.0f, 0.0f, p.pos.z); break; 
			}
			glVertex2f(p.pos.x + t * p.vel.x, p.pos.y + t * p.vel.y);
		}
		glEnd();
		glDisable(GL_BLEND);
	}

	void render_framebuffer_texture (unsigned i )
	{
		glBindTexture(GL_TEXTURE_2D, fboTex[i]);
		glEnable(GL_TEXTURE_2D);
		glColor3f(1.0f, 1.0f, 1.0f);
		draw_unitsquare();
		glDisable(GL_TEXTURE_2D);
	}

	void render_water ()
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, vertex_buffer); 
		glVertexPointer(3, DATA_TYPE, 4 * TYPE_SIZE, 0);

		glBindBufferARB(GL_ARRAY_BUFFER_ARB, normal_buffer);
		glNormalPointer(DATA_TYPE, 4 * TYPE_SIZE, 0);

		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, element_buffer); 

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, poolBottom);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, fboTex[6]);
			
		water.bind();
		water.set_uniform4f ("eyePosition", eyePos);
		water.set_uniform ("transparent", transparent);
		glDrawElements(GL_TRIANGLES, maxIndex, GL_UNSIGNED_INT, 0);
		water.unbind();

		glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0); 
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);

		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
	}
	void render_transparent_bottom ()
	{
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, poolBottom);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, fboTex[6]);
		bottom.bind();
		bottom.set_uniform4f ("eyePosition", eyePos);	
		glTranslatef (0.0f, 0.0f, -.2f);
		draw_unitsquare();
		glTranslatef (0.0f, 0.0f, 0.2f);
		bottom.unbind();			
	}
	void set_viewport ()
	{
		glViewport(0, 0, resolution, resolution);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0.0, 1.0, 0.0, 1.0);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		glDisable(GL_DEPTH_TEST);
	}
	void render(void)
	{
		set_viewport ();
		glBindFramebuffer(GL_FRAMEBUFFER, fb);  // bind frame buffer 
		render_particles();

		// wave creation begin
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fboTex[0]);
		render_framebuffer(xFilter, GL_COLOR_ATTACHMENT1);		
		glBindTexture(GL_TEXTURE_2D, fboTex[1]);
		render_framebuffer(yFilter, GL_COLOR_ATTACHMENT2);
		render_texture (GL_COLOR_ATTACHMENT2, vertex_buffer);
		// wave creation end
	
		// gradient begin
		glBindTexture(GL_TEXTURE_2D, fboTex[2]);
		render_framebuffer (gradient, GL_COLOR_ATTACHMENT3);
		render_texture (GL_COLOR_ATTACHMENT3, normal_buffer);
		// gradient.get_handle() end
	
		// caustics begin
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, fboTex[2]);
		glActiveTexture(GL_TEXTURE1);
		glBindTexture(GL_TEXTURE_2D, fboTex[3]);
		render_framebuffer(caustics, GL_COLOR_ATTACHMENT4);
		render_texture (GL_COLOR_ATTACHMENT4, cb);
		// caustics end 

		glDrawBuffer(GL_COLOR_ATTACHMENT5);
		glEnable(GL_BLEND);
		glClear(GL_COLOR_BUFFER_BIT);
		glColor3f(CAUSTICS, CAUSTICS, CAUSTICS);
		glEnableClientState(GL_VERTEX_ARRAY);
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, cb); 
		glVertexPointer(2, DATA_TYPE, 4 * TYPE_SIZE, 0);
		glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
		glDrawArrays(GL_POINTS, 0, resolution * resolution);
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisable(GL_BLEND);

		// cfilter begin
		glActiveTexture(GL_TEXTURE0);
		cFilter.bind();
		cFilter.set_uniform ("showTex", displayMode == 7);
		cFilter.unbind();

		glBindTexture(GL_TEXTURE_2D, fboTex[5]);
		render_framebuffer (cFilter, GL_COLOR_ATTACHMENT6);
		// cfilter end
		
		//glActiveTexture(GL_TEXTURE0);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		glDrawBuffer(GL_BACK);
		glEnable(GL_DEPTH_TEST);
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glViewport(0, 0, windowWidth, windowHeight);

		if (displayMode == 0) render_water();
		else render_framebuffer_texture (displayMode - 1);
		if (transparent) render_transparent_bottom();
	}



	void clickToGenerate(int x, int y)
	{
		double theta, phi, top, right, dn, dt;
		double x1, y1, z1, x2, y2, z2, x3, y3;

		theta = cameraH * PI1;
		phi = cameraV * PI1;
		if (phi > -epsilon && phi < epsilon) return;

		x1 = 0.5 + cameraR * cos(theta) * cos(phi);
		y1 = 0.5 + cameraR * sin(theta) * cos(phi);
		z1 = cameraR * sin(phi);

		top = cameraNear * tan(0.5 * cameraFovy * PI1);
		right = top / windowHeight * windowWidth;
		dn = top / windowHeight * (windowHeight - y * 2);
		dt = right / windowWidth * (x * 2 - windowWidth);

		x2 = 0.5 + (cameraR - cameraNear) * cos(theta) * cos(phi) - dn * cos(theta) * sin(phi) - dt * sin(theta);
		y2 = 0.5 + (cameraR - cameraNear) * sin(theta) * cos(phi) - dn * sin(theta) * sin(phi) + dt * cos(theta);
		z2 = (cameraR - cameraNear) * sin(phi) + dn * cos(phi);

		x3 = x1 - z1 / (z2 - z1) * (x2 - x1);
		y3 = y1 - z1 / (z2 - z1) * (y2 - y1);

		if (x3 > 0.0 && x3 < 1.0 && y3 > 0.0 && y3 < 1.0) 
		{
			if (generating) 
			{
				float d = sqrt((x3 - genX) * (x3 - genX) + (y3 - genY) * (y3 - genY));
				if (d < PARTICLE_RADIUS)
					m_particlesystem.generator (x3, y3, MAX_AMP / PARTICLE_RADIUS * d);
				else
					m_particlesystem.generator (x3, y3, MAX_AMP);
			}
			else
				m_particlesystem.generator (x3, y3, MAX_AMP);
			genX = x3; genY = y3;
		}
	}


protected :
	bool test_gpu_feature (const char * expr)
	{
		std::string err = expr + std::string(" is not supported") ;
		TEST (expr, err.c_str());
	}
	void test_gpu(void)
	{
		TEST (glewInit() == GLEW_OK, "GLEW is not supported.")
		const char * features [] = {"GL_VERSION_2_1", 
							  "GL_ARB_vertex_buffer_object", 
							  "GL_ARB_half_float_pixel", 
							  "GL_EXT_framebuffer_multisample"
		};
		for (unsigned i = 0; i < 4; ++i)
			test_gpu_feature (features[i]) ;

// FIX ME HERE. THE LIGHT POSITION
		cameraR = cameraOR; cameraH = cameraOH; cameraV = cameraOV;
		eyePos[0] = cameraR * cos(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
		eyePos[1] = cameraR * sin(cameraH * PI1) * cos(cameraV * PI1) + 0.5f;
		eyePos[2] = cameraR * sin(cameraV * PI1);
		eyePos[3] = 1.0f;

		glEnable(GL_DEPTH_TEST);
	}


	void renderWaveParticles(void)
	{
		int i;
		float t;

		glViewport(windowWidth - 256, windowHeight - 256, 256, 256);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		gluOrtho2D(0.0, 1.0, 0.0, 1.0);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glColor4f(0.2f, 0.2f, 0.2f, fading);
		draw_unitsquare();
		glColor4f(1.0f, 1.0f, 1.0f, fading);
		glBegin(GL_POINTS);
	
		for (i = 0; i < m_particlesystem.get_size(); i++) 
		{
			const particle_type  & p = m_particlesystem[i] ;
			t = m_particlesystem.get_system_time() - p.bt;
			glVertex2f (p.pos.x + p.vel.x * t , p.pos.y + p.vel.y * t);
		}
	
		glEnd();

		char buffer[256];

		glColor4f(1.0f, 0.0f, 0.0f, fading);
		sprintf(buffer,  "Wave Particles");
		glRasterPos2f(0.28f, 0.93f);
		for (char* s = buffer; *s != '\0'; s++)
			glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18, *s);

		if (renderParticles && fading < 1.0f) fading += 0.02f;
		else fading -= 0.02f;

		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glViewport(0, 0, windowWidth, windowHeight);
	}

	void init_framebuffer ()
	{
		int x, y, n = 0;
		for (y = 0; y < resolution - 1; y++) 
		{
			for (x = 0; x < resolution - 1; x++) 
			{
				indices[n] = y * resolution + x;
				indices[n + 1] = indices[n] + 1;
				indices[n + 2] = indices[n + 1] + resolution;
				indices[n + 3] = indices[n];
				indices[n + 4] = indices[n + 2];
				indices[n + 5] = indices[n + 4] - 1;
				n += 6;
			}
		}

		glGenBuffersARB(1, &vertex_buffer);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, vertex_buffer);
		glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, resolution * resolution * 4 * TYPE_SIZE, 0, GL_STREAM_DRAW_ARB);

		glGenBuffersARB(1, &normal_buffer);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, normal_buffer);
		glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, resolution * resolution * 4 * TYPE_SIZE, 0, GL_STREAM_DRAW_ARB);

		glGenBuffersARB(1, &element_buffer);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, element_buffer);
		glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, maxIndex * sizeof(GLuint), indices, GL_STATIC_DRAW_ARB);

		glGenBuffersARB(1, &cb);
		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, cb);
		glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, resolution * resolution * 4 * TYPE_SIZE, 0, GL_STREAM_DRAW_ARB);

		glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0);

		glGenTextures(8, fboTex);
		glGenFramebuffersEXT(1, &fb); 
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
		for (int i = 0; i < 8; i++) 
		{
			glBindTexture(GL_TEXTURE_2D, fboTex[i]); 
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
			glTexImage2D(GL_TEXTURE_2D, 0, DATA_INTERNAL, resolution, resolution, 0, DATA_FORMAT, DATA_TYPE, 0);
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, colorAttachment[i], GL_TEXTURE_2D, fboTex[i], 0);
		}
		glBindTexture(GL_TEXTURE_2D, fboTex[6]); 
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

		glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
		
		Bitmap b;
		b.Load("../Textures/gravel.bmp");

		glGenTextures(1, &poolBottom);
		glBindTexture(GL_TEXTURE_2D, poolBottom);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, b.GetWidth(), b.GetHeight(), 0, GL_RGB, GL_FLOAT, b.toFloat());
	}


	void load_shader ()
	{
		std::string path = "../glsl/" ;
		xFilter.load ((path + "xFilter.vert").c_str(), (path + "xFilter.frag").c_str() );
		yFilter.load ((path + "yFilter.vert").c_str(), (path + "yFilter.frag").c_str() );
		gradient.load ((path + "gradient.vert").c_str(), (path + "gradient.frag").c_str() );
		water.load ((path + "water.vert").c_str(), (path + "water.frag").c_str() );
		bottom.load ((path + "bottom.vert").c_str(), (path + "bottom.frag").c_str() );
		caustics.load ( (path + "caustics.vert").c_str(), (path + "caustics.frag").c_str() );
		cFilter.load ((path + "cFilter.vert").c_str(), (path + "cFilter.frag").c_str());
	}
	void init_shader()
	{
		xFilter.bind();
		xFilter.set_uniform ("size", resolution) ;
		xFilter.set_uniform ("pr", PARTICLE_RADIUS) ;
		xFilter.unbind();

		yFilter.bind();
		yFilter.set_uniform ("size", resolution) ;
		yFilter.set_uniform ("pr", PARTICLE_RADIUS) ;
		yFilter.unbind();

		gradient.bind();
		gradient.set_uniform ("size", resolution) ;
		gradient.unbind();

		water.bind();
		water.set_uniform4f ("lightPosition", lightPos);
		water.set_uniform ("bottomTex", 0);
		water.set_uniform ("causticsTex", 1);
		water.unbind();

		bottom.bind();
		bottom.set_uniform ("bottomTex", 0);
		bottom.set_uniform ("causticsTex", 1);
		bottom.unbind();

		caustics.bind();
		caustics.set_uniform4f ("lightPosition", lightPos);
		caustics.set_uniform ("hftex" , 0);
		caustics.set_uniform ("ntex",  1);
		caustics.set_uniform ("size", resolution);
		caustics.unbind();

		cFilter.bind();
		cFilter.set_uniform ("causticsTex", 0);
		cFilter.set_uniform ("size", resolution);
		cFilter.set_uniform ("causticsIntensity", CAUSTICS);
		cFilter.unbind();
	}

protected :
	particle_system <float> m_particlesystem ;
private :
	Shader xFilter, yFilter, gradient, water, caustics, bottom, cFilter;

};

#endif
